import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.filechooser.FileFilter;
import javax.imageio.ImageIO;
import com.sun.image.codec.jpeg.*;

public class Canvas extends JPanel implements MouseMotionListener, MouseListener {
	
	private boolean startHandle = false;
	private double m1,m2,n1,n2,m;
	private ToolBox tb;
	private ColorBox cb;
	private int status;
	private int x1, y1, x2, y2;
	private int prevDragX , prevDragY;
	private boolean isFilled;
	private boolean isSelected;
	private boolean drag;
	private boolean show;
	private boolean move;
	public ArrayList<JShape2D> shapes;
	private JShape2D currentShape;
	public JShape2D selectedShape;
	private JShape2D prevShape;
	private Rectangle2D.Double[] shapehandle;
	private Rectangle2D.Double[] lineHandle;
	private Rectangle2D.Double currentHandle;
	private double S;
    private Rectangle net;
	private Graphics2D g2;
	private File _file; 
	private BufferedImage image;
	private String extension;
	private JFileChooser fileChooser;
	private JPEGFilter jpeg;
	private JPGFilter jpg;
	private CS232Filter cs232;
	public JLabel statusPane;
	
	
	protected final static int SELECT=0,LINE=1,RECTANGLE=2,OVAL=3,ERASER=4;
	
	public Canvas(ToolBox t, ColorBox c) {
		tb = t;
		cb = c;
		t.parent = this;
		c.parent = this;
		status = t.status;
		isFilled = false;
		isSelected = false;
		drag = false;
		show = false;
		move = false;
		shapes = new ArrayList<JShape2D>();
		shapehandle = new Rectangle2D.Double[9];
		lineHandle = new Rectangle2D.Double[3];
		S = 10.0;
        net = new Rectangle(10, 10);
		_file = new File ("untitled");
		
		statusPane = new JLabel();
		statusPane.setPreferredSize(new Dimension(800, 20));
		statusPane.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		
		addMouseListener(this);
		addMouseMotionListener(this);
		setBackground(Color.WHITE);
		setBorder(BorderFactory.createLineBorder(Color.BLACK));
		setPreferredSize(new Dimension(746,600));
	}
	
	public void paintComponent(Graphics g) {	
		super.paintComponent(g);
		g2 = (Graphics2D)g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_ROUND,
										   BasicStroke.JOIN_ROUND));
		for(JShape2D s: shapes) {
			Shape shape;
			int x1 = s.getStart().x;
			int y1 = s.getStart().y;
			int x2 = s.getEnd().x;
			int y2 = s.getEnd().y;
			if(s.getType() == JShape2D.ShapeType.LINE) { 
				shape = new Line2D.Float(x1,y1,x2,y2); 
				s.setShape(shape);
			}
			else if(s.getType() == JShape2D.ShapeType.RECTANGLE) { 
				shape = new Rectangle2D.Float(x1,y1,x2,y2); 
				s.setShape(shape);
			}
			else if(s.getType() == JShape2D.ShapeType.ELLIPSE)	{ 
				shape = new Ellipse2D.Float(x1,y1,x2,y2);
				s.setShape(shape);
			}
			else { shape = new Rectangle2D.Float(x1,y1,x2,y2); }

			g2.setColor(s.getColor());
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, s.getOpacity()));
			if(s.isFilled()) {
				g2.fill(shape);
			} 
			else {
				g2.draw(shape);
			}
			if(s.isSelected()) { 
				if(s.getType() == JShape2D.ShapeType.LINE) { 
					drawHandles(g2, s.getStart(), s.getEnd(), s.getCenter());
				}
				else { drawHandles(g2, shape.getBounds2D()); }
			}
		}
    }
	
	private JShape2D drawShape() {
		JShape2D s = new JShape2D();
		int curX = x1;
		int curY = y1;
		int	curX2 = x2;
		int	curY2 = y2;
		if(x1>x2) {
			int temp = x2;
			x2 = x1;
			curX = temp;
		}
		if(y1>y2) {
			int temp = y2;
			y2 = y1;
			curY = temp;
		}
		int width = x2 - curX;
		int height = y2 - curY;
		switch(status) {
			case Canvas.LINE:
				s.setType(JShape2D.ShapeType.LINE);
				s.setXY(x1, y1);
				s.setStart(new Point(x1, y1));
				s.setEnd(new Point(curX2, curY2));
				s.setCenter(new Point(x1 + (curX2 - x1)/2, y1 + (curY2 - y1)/2));
				break;
			case Canvas.RECTANGLE:
				s.setType(JShape2D.ShapeType.RECTANGLE);
				s.setXY(curX, curY);
				s.setStart(new Point(curX, curY));
				s.setEnd(new Point(width, height));
				s.setCenter(new Point(width/2, height/2));
				s.setFilled(isFilled);
				break;
			case Canvas.OVAL:
				s.setType(JShape2D.ShapeType.ELLIPSE);
				s.setXY(curX, curY);
				s.setStart(new Point(curX, curY));
				s.setEnd(new Point(width, height));
				s.setCenter(new Point(width/2, height/2));
				s.setFilled(isFilled);
				break;
			case Canvas.ERASER:
				s.setType(JShape2D.ShapeType.RECTANGLE);
				s.setStart(new Point(curX, curY));
				s.setEnd(new Point(width, height));
				s.setFilled(true);
				break;
			}
		if(status == Canvas.ERASER) {
			s.setColor(Color.WHITE);
			s.setOpacity(1.0f);
		}
		else {
			s.setColor(this.getForeground());
			s.setOpacity(0.6f);
		}
		return s;
	}
	
	public JShape2D clickedShape(Point p) {
		for(int i = shapes.size() - 1; i >= 0; i--) {
			JShape2D s = (JShape2D)shapes.get(i);
			if(s.getType() == JShape2D.ShapeType.LINE) {
				if (s.getShape().intersects(net)) {
					s.setLoc(i);
					s.setSelected(true);
		            return s;
				}
				else { s.setSelected(false); }
			}
			else {
				if (s.getShape().contains(p.x, p.y)) {
					s.setLoc(i);
					s.setSelected(true);
					return s;
				}
				else { s.setSelected(false); }
			}
		}
		return null;
	}
	//************************************************************Shape Handles********************************************************************//
	public void drawHandles(Graphics2D g2D, Point head, Point tail, Point center) {
		lineHandle[0] = new Rectangle2D.Double(head.x - S/2, head.y - S/2, S, S);
		lineHandle[1] = new Rectangle2D.Double(tail.x - S/2, tail.y - S/2, S, S);
		lineHandle[2] = new Rectangle2D.Double(center.x - S/2, center.y - S/2, S, S);
		
		g2D.setColor(Color.RED);
		for(int i = 0; i< lineHandle.length; i++){
			g2D.draw(lineHandle[i]);
		}
	}
	
	public void drawHandles(Graphics2D g2D, Rectangle2D r) {
		double x = r.getX();
		double y = r.getY();
		double w = r.getWidth();
		double h = r.getHeight();
		shapehandle[0] = new Rectangle2D.Double(x - S/2, y - S/2, S, S);
		shapehandle[1] = new Rectangle2D.Double(x + w * 0.5 - S/2, y - S/2, S, S);
		shapehandle[2] = new Rectangle2D.Double(x + w - S/2, y - S/2, S, S);
		shapehandle[3] = new Rectangle2D.Double(x - S/2, y + h * 0.5 - S/2, S, S);
		shapehandle[4] = new Rectangle2D.Double(x + w - S/2, y + h * 0.5 - S/2, S, S);
		shapehandle[5] = new Rectangle2D.Double(x - S/2, y + h - S/2, S, S);
		shapehandle[6] = new Rectangle2D.Double(x + w * 0.5 - S/2, y + h - S/2, S, S);
		shapehandle[7] = new Rectangle2D.Double(x + w - S/2, y + h - S/2, S, S);
		shapehandle[8] = new Rectangle2D.Double(x + w/2 - S/2, y + h/2 - S/2, S, S);
		
		g2D.setColor(Color.RED);
		for(int i = 0; i< shapehandle.length; i++){
			g2D.draw(shapehandle[i]);
		}
    }
	
	public void newStatus(int st) { status = st; }
	
	public Color getForeGroundColor() { return this.getForeground(); }
 
	public void setForeGroundColor(Color c) { this.setForeground(c);}
 
	public boolean getFill() { return isFilled; }
	
	public void setFill(boolean f) { isFilled = f; }
	
	public void setBackGroundColor(Color c) { this.setBackground(c); }
	
	public void clearCanvas() { this.shapes.clear(); }
	
	public void undoLastShape() {
		int shapeCount = shapes.size();
		if (shapeCount!=0) {
			shapes.remove(shapeCount-1);
			repaint();
		}
	}
	//**************************************************************Mouse Event Handlers***********************************************************//
	public void mousePressed(MouseEvent e) {
		e.consume();
		Point p = e.getPoint();
		x1=e.getX();
		y1=e.getY();
		x2=e.getX();
		y2=e.getY();
		if(status != Canvas.SELECT) {
			currentShape = this.drawShape();
			shapes.add(currentShape);
			for( int i = shapes.size() - 1; i >= 0; i-- ) {
				JShape2D s = (JShape2D)shapes.get(i);
				s.setSelected(false); 
			}
			repaint();
		}
		else {
			net.setFrameFromCenter(p.x, p.y, p.x + S/2, p.y + S/2);
			selectedShape = clickedShape(new Point(x1, y1));
			if(selectedShape != null) {
				shapes.remove(selectedShape.getLoc());
				shapes.add(selectedShape);
				repaint();
				show = true;
			}
			else { show = false; }
			if(show) {
				statusPane.setText(" Drag Center to Move.     Double-Click to Fill.     Right-Click to Unfill.");
				try {
					if(selectedShape.getType() != JShape2D.ShapeType.LINE) {
						if(shapehandle[8].contains(p)) { drag = true; }
						else { drag = false; } 
					}
					else {
						if(lineHandle[2].contains(p)) { drag = true; }
						else { drag = false; }
					}
				}
				catch(Exception ev) {}
			}
			else { statusPane.setText(""); }
			////////////////////////////////////////////////////
			for(int i = 0; i < 2; i++) {
				if(lineHandle[i].contains(p)) { 
					startHandle = (i == 0);
					currentHandle = lineHandle[i];
					move = true; 
				}
				else { 
					currentHandle = null;
					move = false; 
				}
				return;
			}
		}
	}
	
	public void mouseDragged(MouseEvent e) {
		e.consume();
		Point p = e.getPoint();
		x2 = e.getX();
		y2 = e.getY();
		if(currentShape != null) {
			this.shapes.set(this.shapes.size()-1, drawShape());
		}
		if(selectedShape != null) {
			if(drag) {
				if(selectedShape.getType() == JShape2D.ShapeType.LINE) {
					m1 = selectedShape.getStart().getX();
					n1 = selectedShape.getStart().getY();
					m2 = selectedShape.getEnd().getX();
					n2 = selectedShape.getEnd().getY();
					m = (n2-n1)/(m2-m1);
					if(m < 0) {
						selectedShape.setStart(new Point(p.x - selectedShape.getShape().getBounds().width/2,
														 p.y + selectedShape.getShape().getBounds().height/2));
						selectedShape.setEnd(new Point(p.x + selectedShape.getShape().getBounds().width/2,
													   p.y - selectedShape.getShape().getBounds().height/2));
					}
					else {
						selectedShape.setStart(new Point(p.x - selectedShape.getShape().getBounds().width/2,
														 p.y - selectedShape.getShape().getBounds().height/2));
						selectedShape.setEnd(new Point(p.x + selectedShape.getShape().getBounds().width/2,
													   p.y + selectedShape.getShape().getBounds().height/2));
					}
					selectedShape.setCenter(new Point(p.x, p.y));
				}
				else {
					selectedShape.setStart(new Point(p.x - selectedShape.getEnd().x/2,
													 p.y - selectedShape.getEnd().y/2));
				}
			}
			/////////////////////////////////////////////////////////////
			if(move) {
				if(startHandle) {
					selectedShape.setStart(e.getPoint());
				}
				else if(!startHandle) {
					selectedShape.setEnd(e.getPoint());
				}
			}
		}
		repaint();
	}
	
	public void mouseReleased(MouseEvent e) {
		e.consume();
		x2=e.getX();
		y2=e.getX();
		currentShape = null;
		prevShape = selectedShape;
		selectedShape = null;
		drag = false;
		repaint();
	}
	
	public void mouseClicked(MouseEvent e) {
		if(status == Canvas.SELECT) {
			if(e.getClickCount() == 2) {
				prevShape.setColor(this.getForeground());
				prevShape.setFilled(true);
				repaint();
			}
			else if(e.getButton() == MouseEvent.BUTTON3) {
				prevShape.setFilled(false);
			}
		}
	}
	public void mouseMoved(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	//*******************************************************Open and Save Codes*******************************************************************//
	public void fileChooser() {
		fileChooser = new JFileChooser();
		fileChooser.setCurrentDirectory(new File("."));
		jpeg = new JPEGFilter();
		jpg = new JPGFilter();
		cs232 = new CS232Filter();
		fileChooser.addChoosableFileFilter(jpeg);
		fileChooser.addChoosableFileFilter(jpg);
		fileChooser.addChoosableFileFilter(cs232);
		fileChooser.setAcceptAllFileFilterUsed(true);
	}
	
	public boolean openFile() {
		fileChooser();
		fileChooser.setFileFilter(cs232);
		int result = fileChooser.showOpenDialog(this);
		if(result == JFileChooser.CANCEL_OPTION) { return true; }
		else if(result == JFileChooser.APPROVE_OPTION) {
			openCS232();
			return true;
		}
		else { return false; }
	}
	
	public boolean saveFile() {
		fileChooser();
		fileChooser.setFileFilter(cs232);
		fileChooser.setSelectedFile(_file);
		int result = fileChooser.showSaveDialog(this);
		if(result == JFileChooser.CANCEL_OPTION) { return true; } 
		else if(result == JFileChooser.APPROVE_OPTION) {
			_file = fileChooser.getSelectedFile();
			if(_file.exists() ) {
				int response = JOptionPane.showConfirmDialog (this,"Overwrite existing file?","Confirm Overwrite", 
																	JOptionPane.OK_CANCEL_OPTION,
																	JOptionPane.QUESTION_MESSAGE);
				if(response == JOptionPane.CANCEL_OPTION) { return false; }
			}
			FileFilterExtension currentFilterExtesion = (FileFilterExtension) fileChooser.getFileFilter();
			extension = currentFilterExtesion.getExtension();
			if(_file.getName().toLowerCase().endsWith(extension)==false) {
				_file = new File(_file.getAbsolutePath()+extension); 
			}
			if(extension.equals(".jpeg") || extension.equals(".jpg"))
				saveAsJPEG();
			else if(extension.equals(".cs232"))
				saveAsCS232();
			return true;
		} 
		else { return false; }	
	}
	
	public void openCS232() {
		try {
				_file = fileChooser.getSelectedFile();
				ObjectInputStream file_in =  new ObjectInputStream(new FileInputStream(_file));
				if(file_in!=null) { 
					this.shapes = (ArrayList<JShape2D>) file_in.readObject();
					repaint();
				}
				file_in.close();
			}
		catch(Exception e) { e.printStackTrace(); }
	}
	
	public void saveAsJPEG() {
		try {
				FileOutputStream file_out = new FileOutputStream(_file);
				int w = this.getWidth(), h = this.getHeight();
				image = new BufferedImage(w, h,
					BufferedImage.TYPE_INT_RGB);
				Graphics2D g2 = image.createGraphics();
				paintComponent(g2);
				g2.dispose();
				JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(file_out);
				encoder.encode(image);
				file_out.close();
			} 
		catch(Exception e) { e.printStackTrace(); }
	}
	
	public void saveAsCS232() {
		try {
				ObjectOutputStream file_out =  new ObjectOutputStream(new FileOutputStream(_file));
				file_out.writeObject(shapes);
				file_out.close();
			} 
		catch(Exception e) { e.printStackTrace(); }
	}
	
	public interface FileFilterExtension { public String getExtension(); }
	
	public class JPEGFilter extends FileFilter implements FileFilterExtension, Serializable {
	
		private Utils u = new Utils();
		
	    public boolean accept(File f) {
	        if(f.isDirectory()) { return true; }
	        String extension = u.getExtension(f);
	        if(extension != null) {
	            if(extension.equals("jpeg")) {
	                return true;
	            } 
				else { return false;}
	        }
	        return false;
	    } 
		
	    public String getDescription() { return "JPEG(*.jpeg)"; }
		
		public String getExtension() { return ".jpeg"; }	
	}
	
	public class JPGFilter extends FileFilter implements FileFilterExtension, Serializable {
	
		private Utils u = new Utils();
		
	    public boolean accept(File f) {
	        if(f.isDirectory()) { return true; }
	        String extension = u.getExtension(f);
	        if(extension != null) {
	            if(extension.equals("jpg")) {
	                return true;
	            } 
				else { return false;}
	        }
	        return false;
	    } 
		
	    public String getDescription() { return "JPG(*.jpg)"; }
		
		public String getExtension() { return ".jpg"; }	
	}
	
	public class CS232Filter extends FileFilter implements FileFilterExtension, Serializable{
	
		private Utils u = new Utils();
		
	    public boolean accept(File f) {
	        if(f.isDirectory()) { return true; }
	        String extension = u.getExtension(f);
	        if(extension != null) {
	            if(extension.equals("cs232") ){ return true; }
				else { return false; }
	        }
	        return false;
	    }
		
	    public String getDescription() { return "CS232(*.cs232)"; }
		
		public String getExtension() { return ".cs232"; }	
	}
	
	public class Utils implements Serializable{
		
	    public String getExtension(File f) {
	        String ext = null;
	        String s = f.getName();
	        int i = s.lastIndexOf('.');
	        if(i > 0 &&  i < s.length() - 1) {
	            ext = s.substring(i+1).toLowerCase();
	        }
	        return ext;
	    }
		
	    public ImageIcon createImageIcon(String path) {
	        java.net.URL imgURL = Utils.class.getResource(path);
	        if(imgURL != null) { return new ImageIcon(imgURL); } 
			else {
	            System.err.println("Couldn't find file: " + path);
	            return null;
	        }
	    }
	}
}